#include "StdAfx.h"
#include "MatchTemplate.h"
#include "Pattern.h"

namespace SpikingNeuronLib
{
	// Public Members -------------------------------------------------------------------------------------------

	MatchTemplate::MatchTemplate(int templateID, Pattern^ pattern, int jitter, int matchThreshold)
	{
		_matchThreshold = matchThreshold;

		TemplateID = templateID;
		UniqueID = pattern->UniqueID;

		_jitter = jitter;
		_firingEvents = pattern->EventList;
		_matchedEvents = gcnew array<bool>(_firingEvents->Count);

		// calculate the min and max time offsets
		_offsetMin = System::Int32::MaxValue;
		_offsetMax = System::Int32::MinValue;

		for (int i = 0; i < _firingEvents->Count; i++)
		{
			int time = _firingEvents[i].Time;
			if (time < _offsetMin)
				_offsetMin = time;

			if (time > _offsetMax)
				_offsetMax = time;
		}
		_offsetMin -= jitter;
		_offsetMax++;
		_timespan = _offsetMax - _offsetMin + _jitter;

		// create an array of times for each neuron (including jitter)
		_jitterRange = _jitter * 2 + 1;
		_ranges = gcnew Dictionary<FiringEvent, array<int>^>();
		for (int i = 0; i < _firingEvents->Count; i++)
		{
			int time = _firingEvents[i].Time;
			array<int>^ timeRange = gcnew array<int>(_jitterRange);		// match within these times
			for (int j = -_jitter, k = 0; j < _jitter + 1; j++, k++)
			{
				timeRange[k] = time - _offsetMin + j;
			}

			_ranges[_firingEvents[i]] = timeRange;
		}

		// storage for match aggregation
		_matchAggregator = gcnew array<MatchIndex>(_jitterRange);
		_aggregatorIndex = 0;
		_previousTimeIndex = 0;
	}

	String^ MatchTemplate::ToString()
	{
		return Pattern::ConvertToString(this->Template);
	}

	array<array<int>^>^ MatchTemplate::GetFiringsArray()
	{
		array<array<int>^>^ firingsJagged = gcnew array<array<int>^>(_firingEvents->Count);

		for (int i = 0; i < _firingEvents->Count; i++)
		{
			FiringEvent firingEvent = _firingEvents[i];
			firingsJagged[i] = gcnew array<int> { firingEvent.Time, firingEvent.NeuronIndex };
		}

		return firingsJagged;
	}

	array<int>^ MatchTemplate::GetRange(FiringEvent firingEvent)
	{
		return _ranges[firingEvent];
	}

	MatchRaster MatchTemplate::GetMatch(array<int, 2>^ dataRaster, int offset)
	{
		array<array<int>^>^ dataMatch = gcnew array<array<int>^>(_ranges->Count);	// for each unique neuron index
		array<array<int>^>^ templateMatch = gcnew array<array<int>^>(_ranges->Count);	// for each unique neuron index

		Dictionary<FiringEvent, array<int>^>::KeyCollection::Enumerator ^enumerator = _ranges->Keys->GetEnumerator();
		int matchIndex = 0;
		while (enumerator->MoveNext())
		{
			FiringEvent firingEvent = enumerator->Current;
			int nindex = firingEvent.NeuronIndex;

			dataMatch[matchIndex] = gcnew array<int>(_timespan + 1);
			templateMatch[matchIndex] = gcnew array<int>(_timespan + 1);

			// store neuron indices in the first column
			dataMatch[matchIndex][0] = nindex;
			templateMatch[matchIndex][0] = nindex;

			// copy data raster
			// offset ranges from 0 to RasterTimeAxisLength-1 (1999)
			for (int i = 1; i <= _timespan; i++)
			{
				int time = offset + i - 1;
				if (time < RasterTimeAxisLength)
				{
					dataMatch[matchIndex][i] = dataRaster[nindex, time];
				}
			}

			// set template raster
			int jitterRange = _jitter * 2 + 1;
			for (int i = 0; i < _ranges[firingEvent]->Length; i += jitterRange)
			{
				array<int>^ eventRange = _ranges[firingEvent];
				int time = eventRange[i + _jitter];
				//int time = _ranges[firingEvent][i + _jitter];
				templateMatch[matchIndex][time + 1] = 1;
			}

			matchIndex++;
		}

		return MatchRaster(dataMatch, templateMatch);
	}

	// Flush the aggregation buffer
	void MatchTemplate::AggregationFlush(String^ tag)
	{
		if (_aggregatorIndex > 0)
		{
			ReportMatch(tag);
		}
	}

	void MatchTemplate::Match(String^ tag, array<int, 2>^ spikeRaster, int rasterIndex, int timeIndex)
	{
		_eventMatches = 0;

		for (int i = 0; i < _firingEvents->Count; i++)
		{
			bool eventMatch = false;
			_matchedEvents[i] = false;

			FiringEvent firingEvent = _firingEvents[i];
			array<int>^ range = _ranges[firingEvent];

			for (int j = 0; j < range->Length; j++)
			{
				if (spikeRaster[firingEvent.NeuronIndex, (rasterIndex + range[j]) % RasterTimeAxisLength])
				{
					eventMatch = true;
					break;
				}
			}

			if (eventMatch)
			{
				_eventMatches++;
				_matchedEvents[i] = true;
			}
		}

		if (_eventMatches > _matchThreshold)
		{
			using namespace System::Text;

			// Choose the best match in a consecutive sequence of matches to this template
			// Report a match when either:
			//	1. the next match is not part of a temporal sequence of matches i.e. there are intervening time steps
			//	2. the maximum time range over which aggregation occurs is reached

			if ((_aggregatorIndex != 0) && (_previousTimeIndex + 1 != timeIndex))
			{
				// current match is not part of a consecutive sequence of matches
				ReportMatch(tag);
			}

			// add the current match to a match aggregation buffer
			array<bool>^ matchedEvents = (array<bool>^) _matchedEvents->Clone();
			_matchAggregator[_aggregatorIndex++] = MatchIndex(timeIndex, _eventMatches, matchedEvents);

			if (_aggregatorIndex == _jitterRange)
			{
				// limit of aggregation range; aggregation buffer is full
				ReportMatch(tag);
			}

			_previousTimeIndex = timeIndex;
		}
	}

	// Report an aggregated match
	void MatchTemplate::ReportMatch(String^ tag)
	{
		// report on the match with the largest count
		int maxCount = 0;
		int maxIndex = 0;
		for (int i = 0; i < _aggregatorIndex; i++)
		{
			if (_matchAggregator[i].MatchCount > maxCount)
			{
				maxCount = _matchAggregator[i].MatchCount;
				maxIndex = i;
			}
		}

		// build a match result
		array<bool>^ matchedEvents = _matchAggregator[maxIndex].MatchedEvents;
		StringBuilder^ matchPattern =  gcnew StringBuilder(_firingEvents->Count);
		array<FiringEvent>^ coincidentFiringEvents = gcnew array<FiringEvent>(_matchAggregator[maxIndex].MatchCount);

		int arrayIndex = 0;
		for (int i = 0; i < _firingEvents->Count; i++)
		{
			if (matchedEvents[i])
			{
				matchPattern->Append("1");
				coincidentFiringEvents[arrayIndex++] = _firingEvents[i];
			}
			else
			{
				matchPattern->Append("0");
			}
		}

		_aggregatorIndex = 0;
		_previousTimeIndex = 0;

		// callback to scanner
		_matchHandler(tag, this->TemplateID, this->UniqueID,
			_matchAggregator[maxIndex].TimeIndex,
			_matchAggregator[maxIndex].MatchCount,
			matchPattern->ToString(), coincidentFiringEvents);
	}

	void MatchTemplate::AddMatchHandler(AggregatedMatchEventHandler^ matchHandler)
	{
		_matchHandler = matchHandler;
	}
}
